package webreg.model;

import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.Iterator;
import java.util.Vector;

import javax.xml.bind.annotation.XmlAccessOrder;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorOrder;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;

import org.apache.log4j.Logger;

import webreg.model.exceptions.EmptyNameException;
import webreg.model.exceptions.EmptySurnameException;
import webreg.model.exceptions.NegativNumberException;

/** Implementation for athlets
 * 
 * @author STKNOLL
 */
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
@XmlAccessorType(XmlAccessType.NONE)
public class Athlet implements Cloneable{

	@XmlAttribute(name = "id")
	private int mId;
	@XmlAttribute(name = "surname")
	private String mSurname;
	@XmlAttribute(name = "name")
	private String mName;
	@XmlAttribute(name = "yearOfBirth")
	private int mYearOfBirth;
	@XmlAttribute (name = "sex")
	private Sex mSex;
	@XmlElement (name = "classification")
	private AgeClassification mClassAge;
	@XmlElementWrapper (name = "registeredDisciplins")
	@XmlElement (name = "disciplin")
	private Vector<Disciplin> mDisciplins;
	private AgeTable mAgeTable;
	private static Logger logger = Logger.getLogger( Athlet.class );
	
	/** Constructor for an athlet 
	 */
	public  Athlet() { 
		mId = 0;
		mSurname = "";
		mName = "";
		mYearOfBirth = 0;
		mSex = Sex.MALE;
		mClassAge = new AgeClassification();
		mDisciplins = new Vector<Disciplin>();
		mAgeTable = new AgeTable();
	}
		
	/** Constructor for an athlet
	 * 
	 * Creates an athlet with minimal information.
	 * 
	 * @param surname The surname of the athlet
	 * @param name The name of the athlet
	 * @param yearOfBirth The year of birth
	 * @param sex The sex of the athlet
	 * @throws EmptySurnameException 
	 * @throws EmptyNameException 
	 * @throws NegativNumberException 
	 */
	public Athlet(String surname, String name, int yearOfBirth, Sex sex) throws EmptySurnameException, EmptyNameException, NegativNumberException {
		this();
		setSurname(surname);
		setName(name);
		setYearOfBirth(yearOfBirth);
		setSex(sex);
	}

	/** Constructor for an athlet
	 * 
	 * @param id The startpass ID of the athlet
	 * @param surname The surname of the athlet
	 * @param name The name
	 * @param yearOfBirth the year of birth
	 * @param sex The sex of the athlet
	 * @param class The age-classification of the athlet
	 * @param disciplins A vector of disciplins for the athlet
	 * @param ageTableToUse The age table the athlet uses for age classification
	 * @throws EmptyNameException 
	 * @throws EmptySurnameException 
	 * @throws NegativNumberException 
	 */
	public Athlet(int startPassportId, String surname, String name, int yearOfBirth, Sex sex, AgeClassification classAge, Vector<Disciplin> disciplins, AgeTable ageTableToUse) throws EmptySurnameException, EmptyNameException, NegativNumberException {
		this(surname, name, yearOfBirth, sex);
		setId( startPassportId );
		setClassAge(classAge);
		setDisciplins(disciplins);
		setAgeTable(ageTableToUse);
	}

	/** Copy constructor
	 * 
	 * Generate an athlet based on an already existing one
	 *
	 * @param toCopy The athlet to copy
	 * @throws EmptyNameException 
	 * @throws EmptySurnameException 
	 * @throws NegativNumberException 
	 */
	public Athlet(Athlet toCopy) {
		this();
		mId = toCopy.mId;
		mSurname = toCopy.mSurname;
		mName = toCopy.mName;
		mYearOfBirth = toCopy.mYearOfBirth;
		mSex = toCopy.mSex;
		mClassAge = toCopy.mClassAge;
		mDisciplins = toCopy.mDisciplins;
		mAgeTable = toCopy.mAgeTable;
	}

	/** Implementation of the equals function
	 * 
	 * @param toCompare The value to compare
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(Athlet toCompare) {
		// first compare non-string values (faster when finding an error)
		if ( this.mId != toCompare.mId ) return false;
		if ( this.mYearOfBirth != toCompare.mYearOfBirth ) return false;
		if ( this.mSex.equals(toCompare.mSex) == false ) return false;
		// perform string compare after non-string compares (speed reason)
		if ( this.mClassAge.equals(toCompare.mClassAge) == false ) return false;
		if ( this.mSurname.equals(toCompare.mSurname) == false ) return false;
		if ( this.mName.equals(toCompare.mName) == false ) return false;
		if ( this.mAgeTable.equals(toCompare.mAgeTable) == false ) return false;
		
		if ( (mDisciplins != null) && (toCompare.mDisciplins != null) ) {
			// test the size first
			if (this.mDisciplins.size() != toCompare.mDisciplins.size()) return false;
			
			// compare the elements
			Iterator<Disciplin> iter = this.mDisciplins.iterator();
			Iterator<Disciplin> iter2 = toCompare.mDisciplins.iterator();
			for (; iter.hasNext() && iter2.hasNext(); ) {
				Disciplin disc1 = (Disciplin)iter.next();
				Disciplin disc2 = (Disciplin)iter2.next();
				if (disc1.equals(disc2) == false) return false;
			}			
		}
		
		return true;
	}

	
	/** Returns the name of the athlet
	 * 
	 * @return the name
	 */
	public String getName() {
		return mName;
	}

	/** Sets the name of the athlet
	 * 
	 * @param name The name to set
	 * @throws EmptyNameException 
	 */
	public void setName(String name) throws EmptyNameException {
		if ( (name == null) || (name.trim().equals("") ) ) {
			throw new EmptyNameException("Athlet");
		}
		this.mName = name;
	}

	/** Returns the sex of the athlet
	 * 
	 * @return The sex of the athlet
	 */
	public Sex getSex() {
		return mSex;
	}

	/** Sets the sex of the athlet
	 * 
	 * @param sex The sex to set
	 */
	public void setSex(Sex sex) {
		this.mSex = sex;
	}
	
	/** Returns the passport id of the athlet
	 * 
	 * @return The id of the athlet
	 */
	public int getId() {
		return this.mId;
	}

	/** Return the id as string
	 * 
	 * @return The id as string
	 */
	public String getIdAsString() {
		String pattern = "00000000";
		DecimalFormat myFormatter = new DecimalFormat(pattern);
		String output = myFormatter.format(mId);
		logger.info(mId + " " + pattern + " " + output);
		return output;
	}
	
	/** Sets the id of the athlet
	 * 
	 * @param id The id to set
	 */
	public void setId(int id) {
		this.mId = id;
	}
	
	/** Sets the id of the athlet from the given string
	 * 
	 * @param id The id to set as string
	 */
	public void setId(String id) {
		if (id.trim().equals("") == false) {
			this.mId = new Integer(id).intValue();
		}
	}
	
	/** Return the surname of the ahtlee
	 * 
	 * @return The surname
	 */
	public String getSurname() {
		return mSurname;
	}
	
	/** Sets the athlets surname
	 * 
	 * @param surname The surname to set
	 * @throws EmptySurnameException 
	 */
	public void setSurname(String surname) throws EmptySurnameException {
		if ( (surname == null) || (surname.trim().equals("") ) ) {
			throw new EmptySurnameException("Athlet " + getName());
		}
		this.mSurname = surname;
	}

	/** Returns the year of birth of the athlet
	 * 
	 * @return The year of birth
	 */
	public int getYearOfBirth() {
		return mYearOfBirth;
	}

	/** Returns the year of birth as string
	 * 
	 * @return The year of birth as string
	 */
	public String getYearOfBirthAsString() {
		return String.valueOf(mYearOfBirth);
	}
	
	/** Sets the year of birth of the athlet
	 * 
	 * @param yearOfBirth The year of birth to set.
	 * @throws NegativNumberException 
	 */
	public void setYearOfBirth(int yearOfBirth) throws NegativNumberException {
		if (yearOfBirth < 0) throw new NegativNumberException(yearOfBirth, "Athlet " + getName());
		this.mYearOfBirth = yearOfBirth;
		setClassAge(defineAgeClass());
	}

	/** Sets the year of birth of the athlet
	 * 
	 * @param yearOfBirth They year of birth as string
	 * @throws NegativNumberException 
	 * @throws NumberFormatException 
	 */
	public void setYearOfBirth(String yearOfBirth) throws NumberFormatException, NegativNumberException {
		setYearOfBirth(new Integer(yearOfBirth).intValue());
	}
	
	/** Gets the age-classification of the athlet
	 * @return The age class of the athlet
	 */
	public AgeClassification getClassAge() {
		return mClassAge;
	}

	/** Sets the age-classification of the athlet
	 * @param ageClass The age-classification to set
	 */
	public void setClassAge(AgeClassification ageClass) {
		this.mClassAge = ageClass;
	}
	
	/** Returns the disciplins of this athlet
	 * @return The disciplins of this athlet in a vector
	 */
	public Vector<Disciplin> getDisciplins() {
		return mDisciplins;
	}

	/** Sets the disciplins of this athlet
	 * @param registeredDisciplins The vector of disciplins for this athlet
	 */
	public void setDisciplins(Vector<Disciplin> registeredDisciplins) {
		mDisciplins = registeredDisciplins;
	}

	/** Returns the age table the athlet uses for age class definition
	 * 
	 * @return The age table the athlet uses
	 */
	public AgeTable getAgeTable(){
		return this.mAgeTable;
	}
	
	/** Sets the age table for the athlet to the given one 
	 * @param ageTableToSet The age table to set
	 */
	public void setAgeTable(AgeTable ageTableToSet) {
		this.mAgeTable = ageTableToSet;
	}
	
	/** Calculates the age-classification based on the year of birth of the athlete
	 * and the actual system date
	 *  
	 * @return The age classification of this athlete or null
	 */
	private AgeClassification defineAgeClass() {
		Calendar rightNow = Calendar.getInstance();
		int yearNow = rightNow.get(Calendar.YEAR);

		int ageToMatch = (yearNow - getYearOfBirth());
		AgeClassification ageClass = new AgeClassification();
		Vector<AgeClassification> ageClasses = mAgeTable.getTable();
		for (Iterator<AgeClassification> iter = ageClasses.iterator(); iter.hasNext(); ) {
			ageClass = (AgeClassification)iter.next();
		    if (ageClass.matchAge(ageToMatch)) {
		    	return ageClass;
		    }
		}
		return ageClass;
	}

	@Override
	public String toString() {
		return getName() + ", " + getSurname() + ", " + getSex().getValue() + ", " + getYearOfBirthAsString() + ", " + getIdAsString();
		
	}

	
	@SuppressWarnings("unchecked")
	@Override
	public Object clone() {
		Athlet newAthlet = new Athlet();
		newAthlet.mId = mId;
		newAthlet.mSurname = mSurname;
		newAthlet.mName = mName;
		newAthlet.mYearOfBirth = mYearOfBirth;
		newAthlet.mSex = mSex;
		newAthlet.mClassAge = (AgeClassification)mClassAge.clone();
		newAthlet.mDisciplins = (Vector<Disciplin>)mDisciplins.clone();
		newAthlet.mAgeTable =(AgeTable)mAgeTable.clone();
		return newAthlet;
	}
	
	public int compareTo(Athlet other) {
		int nameCompare = mName.compareTo(other.mName);
		if (nameCompare != 0) {	return nameCompare; }
		
		int surnameCompare = mSurname.compareTo(other.mSurname);
		if (surnameCompare != 0) { return surnameCompare; }
		
		if (mYearOfBirth < other.mYearOfBirth) { return -1; }
		else if (mYearOfBirth > other.mYearOfBirth) { return 1; }
		
		int sexCompare = mSex.compareTo( other.mSex );
		if (sexCompare != 0) { return sexCompare; }
		
		if ( (mClassAge != null) && (other.mClassAge != null) ) {
			int classCompare = mClassAge.compareTo( other.mClassAge );
			if (classCompare != 0 ) { return classCompare; }
		}
		
		if ( (mDisciplins != null) && (other.mDisciplins != null) ) {	
			Iterator<Disciplin> iter1 = mDisciplins.iterator();
			Iterator<Disciplin> iter2 = other.mDisciplins.iterator();
			for(; iter1.hasNext() && iter2.hasNext(); ) {	
				Disciplin dis1 = (Disciplin)iter1.next();
				Disciplin dis2 = (Disciplin)iter2.next();
				int disComp = dis1.compareTo(dis2);
				if ( disComp != 0) {
					return disComp;
				}			
			}
		}
		
		if ( (mAgeTable != null) && (other.mAgeTable != null) ) {
			int ageTableCompare = mAgeTable.compareTo ( other.mAgeTable );
			if (ageTableCompare != 0 ) { return ageTableCompare; }
		}

		if (mId < other.mId) { return -1; }
		else if (mId > other.mId) { return 1; }

		return 0;
	}
	
}
